//system
#include <sstream>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <deque>
#include <string>
#include <iostream>


//boost
#include <boost/tuple/tuple.hpp>
#include <boost/regex.hpp>

// my headers
#define DEBUG_LEVEL 6
#include <debug_func.h>
#include <blast_results.h>
#include <index_string.h>
#include <portable_timer.h>
#include <codeml_results.h>
#include <progress_indicator.h>
#define _MY_STL_MACROS
#include <stlhelper.h>
#include <ultoa.h>
#include <print_error.h>
#include <open_fstream.h>
#include <seq_pairs.h>
#include <get_filename.h>



#include "get_arg.h"


using std::vector;
using std::pair;
using std::deque;
using std::ofstream;
using std::ostream;
using std::ifstream;
using std::istream;
using std::istringstream;
using std::string;
using std::runtime_error;
using std::cerr;
using std::cin;
using std::cout;
using boost::tie;
using boost::regex;
using boost::match_results;
using boost::regex_search;




//________________________________________________________________________________________

//		do_parse_pairs

//________________________________________________________________________________________
bool get_matching_part_of_identifier(const std::string& str, boost::regex& pattern_match_regex, std::string& match)
{
	match_results<string::const_iterator> found_str;
	if (boost::regex_search(str, found_str, pattern_match_regex, boost::match_default))
	{
		match.assign(found_str[1].first, found_str[1].second);
		return true;
	}
	return false;
}

struct do_parse_pairs
{
	t_index_string&				id_strings;
	std::deque<unsigned>&		best_pairs;
	std::deque<double>&			best_pairs_dist;
	std::deque<string>&			best_pairs_full_id;
	std::deque<string>&			best_pairs_full_other_id;
	std::deque<unsigned>&		best_pairs_is_fragment_match;
	unsigned					cnt_input;
	std::string					comment_letters;
	boost::regex				regex;
	unsigned					cnt_non_matching;
	unsigned					cnt_self_matches;
	std::string					example_non_matching;
	bool						use_regex_match;
	bool						use_coverage;
	bool						coverage_error;

	do_parse_pairs(	t_index_string&			id_strings_,
					std::deque<unsigned>&	best_pairs_,
					std::deque<double>&		best_pairs_dist_,
					std::deque<string>&		best_pairs_full_id_,
					std::deque<string>&		best_pairs_full_other_id_,
					std::deque<unsigned>&	best_pairs_is_fragment_match_,
					const std::string&		comment_letters_,
					const std::string&		regex_str,
					bool					use_coverage_):

				id_strings(id_strings_),
				best_pairs(best_pairs_),
				best_pairs_dist(best_pairs_dist_),
				best_pairs_full_id(best_pairs_full_id_),
				best_pairs_full_other_id(best_pairs_full_other_id_),
				best_pairs_is_fragment_match(best_pairs_is_fragment_match_),
				cnt_input(0),
				comment_letters(comment_letters_),
				regex(regex_str),
				cnt_non_matching(0),
				cnt_self_matches(0),
				use_regex_match(regex_str != "(.*)"),
				use_coverage(use_coverage_),
				coverage_error(false)
	{
	}
		
	void operator()(istream& is, unsigned)
	{
		// get data for one protein pair
		string full_name[2];


		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, full_name[0], full_name[1], comment_line, comment_letters,
							  seq_pairs::ePARSE_IDS_ONLY))
			return;


		double dist;
		is >> dist;

		unsigned coverage = 0;
		if (use_coverage)
			is >> coverage;
		if (coverage > 2)
		{
			coverage_error = true;
			coverage = 0;
		}
		unsigned id_frag[2];
		id_frag[0] = id_frag[1] = 0;
		if (coverage > 0)
		{
			id_frag[coverage - 1] = 1;
		}

		if (!is)
			throw std::runtime_error("The line is not in the correct format");

		// index genes
		++cnt_input;

		//
		// get identifier using regex if necessary
		//
		string name[2];
		for (unsigned i = 0; i < 2; ++i)
		{
			name[i] = full_name[i];
			if (use_regex_match &&
				!get_matching_part_of_identifier(full_name[i], regex, name[i]))
			{
				++cnt_non_matching;
				example_non_matching = full_name[0] + "\t" + full_name[1];
				return;
			}
		}

		// ignore self self matches
		if (name[0] == name[1])
		{
			++cnt_self_matches;
			return;
		}

		unsigned	id[2];
		bool added[2];
		for (unsigned i = 0; i < 2; ++i)
		{
			id[i] = id_strings.index(name[i], added[i]);
		}
		

		for (unsigned i = 0; i < 2; ++i)
		{
			unsigned my_id = id[i];

			unsigned other_index = (i+1)%2;
			if (added[i])
			{
				// For this identifier (id_strings[id1])
				// 		
				//		save its best partner	best_pairs[id1] = id2
				//		save dist to partner	best_pairs_dist[id1] = dist;
				//		save its own name		best_pairs_full_id[id1] = full_name[0];
				//      save partner's name		best_pairs_full_other_id[id1] =
				//      													full_name[1];
				//
				//	This works because size of arrays = id1 + 1 after push_back
				assert(best_pairs.size() == my_id);
				best_pairs.push_back(id[other_index]);
				best_pairs_dist.push_back(dist);
				best_pairs_full_id.push_back(full_name[i]);
				best_pairs_full_other_id.push_back(full_name[other_index]);
				best_pairs_is_fragment_match.push_back(id_frag[i]);
			}
			else
			{
				// Only update this identifier if encounter closer partner (id_strings[id1])
				// and saved fragment state is greater than or same as current
				// fragment state. I.e. full length (lower fragment state)
				// always trumps fragment.
				// 		
				if (best_pairs_dist[my_id] > dist &&
					best_pairs_is_fragment_match[my_id] >= id_frag[i])
				{
					assert(best_pairs.size() < my_id);
					best_pairs_dist[my_id]				= dist;
					best_pairs[my_id]	   				= id[other_index];
					best_pairs_full_id[my_id] 			= full_name[i];
					best_pairs_full_other_id[my_id]		= full_name[other_index];
					best_pairs_is_fragment_match[my_id] = id_frag[i];
				}
			}
		}
	}
};



//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//		Main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888



int main(int argc, char* argv[])
{
	t_program_args args;

	try
	{

		debug_func_cerr(5);
	
		// don't use C streams
		std::ios::sync_with_stdio(false);
	
	
		// get command line arguments
		if (!process_command_line_options(argc, argv, args))
			return 1;
	
		args.VERBOSE(2) << "\tThe regular expression used to extract identifiers will be \"" +
						args.regex + "\"\n";
	
	
		t_index_string			id_strings;
		std::deque<unsigned>	best_pairs;
		std::deque<double>		best_pairs_dist;
		std::deque<string>		best_pairs_full_names;
		std::deque<string>		best_pairs_full_other_names;
		std::deque<unsigned>	best_pairs_is_fragment_match;
		do_parse_pairs parse_pairs(id_strings,  best_pairs, best_pairs_dist, 
								   best_pairs_full_names,
								   best_pairs_full_other_names,
								   best_pairs_is_fragment_match,
								   args.comment_letters,
								   args.regex,
								   args.coverage);

		if (parse_pairs.coverage_error)
		{
			std_print_warning(args.CERR(), "Some coverage values were greater than 2!!");
		}
		
		seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
	
		// write summary
		args.VERBOSE() << "\t" << parse_pairs.cnt_input << "\tpairs parsed.\n";
		args.VERBOSE() << "\t" << id_strings.size() << "\tidentifiers\n";
		if (parse_pairs.cnt_self_matches)
			args.VERBOSE() << "\t" << parse_pairs.cnt_self_matches
							<< "\tpairs discarded because they were self-self matches\n";
		if (parse_pairs.cnt_non_matching)
		{
			args.CERR()	<< parse_pairs.cnt_non_matching
						<< "\tlines did not match the supplied regular expression"
						   "-->" + args.regex + "<--.\nE.g.\n\t" + 
							parse_pairs.example_non_matching + "\n";
		}

		unsigned cnt_output = 0;
		if (args.coverage)
		{
			for (unsigned id = 0; id < id_strings.size(); ++id)
			{
				unsigned other_id = best_pairs[id];

				// only print one of pair
				if (!args.reciprocals				&&
					best_pairs[other_id] == id		&&
					other_id < id)
					continue;
				++cnt_output;
				cout
					<< best_pairs_full_names[id]				<< "\t"
					<< best_pairs_full_other_names[id]			<< "\t"
					<< best_pairs_dist[id]						<< "\t"
					<< best_pairs_is_fragment_match[id]			<< "\n";
			}
		}
		else
			for (unsigned id = 0; id < id_strings.size(); ++id)
			{
				unsigned other_id = best_pairs[id];
	
				// only print one of pair
				if (!args.reciprocals				&&
					best_pairs[other_id] == id		&&
					other_id < id)
					continue;
				++cnt_output;
				cout
					<< best_pairs_full_names[id]				<< "\t"
					<< best_pairs_full_other_names[id]			<< "\t"
					<< best_pairs_dist[id]						<< "\n";
			}
		args.VERBOSE() << "\t" << cnt_output
						<< "\tpairs printed.\n";
	

	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}

	return 0;
}




